package org.oos.util;

import static java.util.logging.Level.ALL;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINER;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 
 * Extension for {@link android.util.Log} to always use {@link Log#TAG} as tag
 * and some other goodness.
 * 
 * @author pablo.gonzalez@11870.com
 * 
 */
public class Log {
	
	private static boolean debug = false;
	
	public static boolean isDebuggable() {
		return debug;
	}
	
	public static void setDebug(boolean debug) {
		Log.debug = debug;
	}

	private static enum LogLevel {
		VERBOSE(2, ALL, "v"), //
		DEBUG(3, FINE, "d"), //
		INFO(4, Level.INFO, "i"), //
		WARN(5, WARNING, "w"), //
		ERROR(6, SEVERE, "e"), //
		ASSERT(7, FINER, "wtf"); //

		private int androidPriority;
		private Level level;

		private String androidMethodName;
		private Method androidMethod;

		static Method nativePrintln;

		private LogLevel(int androidPriority, Level level,
				String androidMethodName) {
			this.androidPriority = androidPriority;
			this.level = level;
			this.androidMethodName = androidMethodName;
		}

		Integer println(String TAG, String msg, Throwable throwable) {
			Integer result = -1;
//			if (logClass != null) {
//				// in android
//				try {
//					// handle all the logs by calling the println_native()
//					// method
//					if (this != ASSERT) {
//						// get the native printnl method and make it accessible
//						if (nativePrintln == null) {
//							nativePrintln = Log.logClass.getMethod(
//									"println_native", int.class, int.class,
//									String.class, String.class);
//							nativePrintln.setAccessible(true);
//						}
//						result = (Integer) nativePrintln.invoke(null, 0,
//								androidPriority, TAG, msg + '\n'
//										+ getStackTraceString(throwable));
//					} else {
//						// handle wft logs calling directly to the wtf() method
//						if (androidMethod == null) {
//							androidMethod = logClass.getMethod(
//									androidMethodName, String.class,
//									String.class, Throwable.class);
//						}
//						result = (Integer) androidMethod.invoke(null, TAG, msg,
//								throwable);
//					}
//				} catch (Exception e) {
//					// report in a separate logger
//					Logger.getLogger("Log")
//							.log(Level.SEVERE, e.getMessage(), e);
//				}
//			}
			// Fallback log
			if (!debug || level == ERROR.level) {
				Logger.getLogger(TAG).log(level, msg, throwable);
			}
			if (throwable instanceof RuntimeException) {
				// log and crash it it's a runtime exception
				throw (RuntimeException)throwable;
			}
			return result;
		}

	}

	private static Class<?> logClass = null;

	public static void setLogClass(Class<?> logClass) {
		Log.logClass = logClass;
	}

	public static final String TAG = "OOS";

	public static int v(String msg) {
		return v(msg, null);
	}

	/**
	 * Send a {@link #VERBOSE} log message and log the exception.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 * @param tr
	 *            An exception to log
	 */
	public static int v(String msg, Throwable tr) {
		// return android.util.Log.v(TAG, msg, tr);
		return LogLevel.VERBOSE.println(TAG, msg, tr);
	}

	public static int v(Throwable tr) {
		return v(tr.getMessage(), tr);
	}

	/**
	 * Send a {@link #DEBUG} log message.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 */
	public static int d(String msg) {
		return d(msg, null);
	}

	/**
	 * Send a {@link #DEBUG} log message and log the exception.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 * @param tr
	 *            An exception to log
	 */
	public static int d(String msg, Throwable tr) {
		return LogLevel.DEBUG.println(TAG, msg, tr);
	}

	public static int d(Throwable tr) {
		return d(tr.getMessage(), tr);
	}

	/**
	 * Send an {@link #INFO} log message.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 */
	public static int i(String msg) {
		return i(msg, null);
	}

	/**
	 * Send a {@link #INFO} log message and log the exception.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 * @param tr
	 *            An exception to log
	 */
	public static int i(String msg, Throwable tr) {
		return LogLevel.INFO.println(TAG, msg, tr);
		// return android.util.Log.i(TAG, msg, tr);
	}

	public static int i(Throwable tr) {
		return i(tr.getMessage(), tr);
	}

	/**
	 * Send a {@link #WARN} log message.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 */
	public static int w(String msg) {
		return w(msg, null);
	}

	/**
	 * Send a {@link #WARN} log message and log the exception.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 * @param tr
	 *            An exception to log
	 */
	public static int w(String msg, Throwable tr) {
		return LogLevel.WARN.println(TAG, msg, tr);
		// return android.util.Log.w(TAG, msg, tr);
	}

	private static Method isLoggableMethod = null;

	/**
	 * Checks to see whether or not a log for the specified tag is loggable at
	 * the specified level.
	 * 
	 * The default level of any tag is set to INFO. This means that any level
	 * above and including INFO will be logged. Before you make any calls to a
	 * logging method you should check to see if your tag should be logged. You
	 * can change the default level by setting a system property: 'setprop
	 * log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>' Where level is either VERBOSE,
	 * DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will turn off all
	 * logging for your tag. You can also create a local.prop file that with the
	 * following in it: 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>' and place that in
	 * /data/local.prop.
	 * 
	 * @param tag
	 *            The tag to check.
	 * @param level
	 *            The level to check.
	 * @return Whether or not that this is allowed to be logged.
	 * @throws IllegalArgumentException
	 *             is thrown if the tag.length() > 23.
	 */
	public static boolean isLoggable(String tag, int level) {
		if (logClass != null) {
			try {
				if (isLoggableMethod == null) {
					isLoggableMethod = logClass.getMethod("isLoggable",
							String.class, int.class);
				}
				return (Boolean) isLoggableMethod.invoke(null, tag, level);
			} catch (Exception e) {
				w(e);
			}
		}
		return false;
	}

	/*
	 * Send a {@link #WARN} log message and log the exception.
	 * 
	 * @param tag Used to identify the source of a log message. It usually
	 * identifies the class or activity where the log call occurs.
	 * 
	 * @param tr An exception to log
	 */
	public static int w(Throwable tr) {
		return w(tr.getMessage(), tr);
		// return android.util.Log.w(TAG, tr);
	}

	/**
	 * Send an {@link #ERROR} log message.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 */
	public static int e(String msg) {
		return e(msg, null);
	}

	/**
	 * Send a {@link #ERROR} log message and log the exception.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 * @param tr
	 *            An exception to log
	 */
	public static int e(String msg, Throwable tr) {
		return LogLevel.ERROR.println(TAG, msg, tr);
		// return android.util.Log.e(TAG, msg, tr);
	}

	public static int e(Throwable tr) {
		return e(tr.getMessage(), tr);
	}

	/**
	 * What a Terrible Failure: Report a condition that should never happen. The
	 * error will always be logged at level ASSERT with the call stack.
	 * Depending on system configuration, a report may be added to the
	 * {@link android.os.DropBoxManager} and/or the process may be terminated
	 * immediately with an error dialog.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 */
	public static int wtf(String msg) {
		return wtf(msg, null);
	}

	/**
	 * What a Terrible Failure: Report an exception that should never happen.
	 * Similar to {@link #wtf(String)}, with an exception to log.
	 * 
	 * @param tr
	 *            An exception to log.
	 */
	public static int wtf(Throwable tr) {
		return wtf(tr.getMessage(), tr);
	}

	/**
	 * What a Terrible Failure: Report an exception that should never happen.
	 * Similar to {@link #wtf(Throwable)}, with a message as well.
	 * 
	 * @param msg
	 *            The message you would like logged.
	 * @param tr
	 *            An exception to log. May be null.
	 */
	public static int wtf(String msg, Throwable tr) {
		return LogLevel.ASSERT.println(TAG, msg, tr);
		// return android.util.Log.wtf(TAG, msg, tr);
	}

	/**
	 * Handy function to get a loggable stack trace from a Throwable
	 * 
	 * @param tr
	 *            An exception to log
	 */
	public static String getStackTraceString(Throwable tr) {
		if (tr == null) {
			return "";
		}
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		tr.printStackTrace(pw);
		return sw.toString();
	}

}
